
package mc

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cast"
	"io"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type Logger struct {
	*logrus.Logger
	tag string
	commonInfo interface{}
	c *gin.Context
}

// 设置日志Tag
func (l *Logger) Tag(tag string) *Logger {
	l.tag = tag
	return l
}

// 设置日志上下文
func (l *Logger) SetContext(c *gin.Context) *Logger{
	l.c = c
	return l
}

// 设置公共字段，只对当前调用的GetLogger内有效
func (l *Logger) SetCommonInfo(commonInfo interface{}) *Logger {
	l.commonInfo = commonInfo
	return l
}

// 运行时日志
func (l *Logger) WithRunFields(info interface{}) *logrus.Entry {
	return l.WithCustomFields(&LogFields{Type:"run",Info: info})
}
// 调试日志
func (l *Logger) WithDebugFields(info interface{}) *logrus.Entry {
	return l.WithCustomFields(&LogFields{Type:"debug",Info: info})
}

// 自定义日志
func (l *Logger) WithCustomFields(logFields interface{}) *logrus.Entry{
	//jsonLogFields, err := json.Marshal(logFields)
	jsonLogFields, err := JsonEncoder(logFields, false)
	var fields logrus.Fields
	if err != nil {
		l.WithFields(logrus.Fields{"type":"error"}).Error(fmt.Sprintf("日志写入-解析日志字段错误：%s", err))
	} else {
		//err = json.Unmarshal(jsonLogFields, &fields)
		if err = json.Unmarshal([]byte(jsonLogFields), &fields); err != nil {
			l.WithFields(logrus.Fields{"type":"error"}).Error(fmt.Sprintf("日志写入-解析日志字段错误：%s", err))
		}
	}
	if l.tag != "" {
		fields["tag"] = l.tag
	}
	if  l.c != nil{
		fields["route"] = l.c.Request.URL.Path
	}
	if  l.commonInfo != nil {
		fields["common_info"] = l.commonInfo
	}
	return l.WithFields(fields)
}

type LogFields struct {
	Type string `json:"type"`
	ExecuteTime string `json:"exc_time"`
	StatusCode int `json:"st_code"`
	Code string `json:"code"`
	Info interface{} `json:"info"`
	CommonInfo interface{}
	Tag string `json:"tag"`
	Route string `json:"route"`
}


type LogRequestFields struct {
	Method string `json:"method"`
	ContentType string `json:"content_type"`
	RequestIp string `json:"req_ip"`
	RequestUri string `json:"req_uri"`
	RequestData interface{} `json:"req_data"`
	RenderType RenderType `json:"render_type"`
}


var loggers map[string]*Logger

var errStaceLogOut  io.Writer
var cronLogOut io.Writer
var SqlLogOut io.Writer = os.Stdout

//
type LogFormatter struct {}
func (s *LogFormatter) Format(entry *logrus.Entry) ([]byte, error){

	var b *bytes.Buffer
	if entry.Buffer != nil {
		b = entry.Buffer
	}else{
		b = &bytes.Buffer{}
	}

	timestamp := time.Now().Local().Format("2006-01-02 15:04:05.999")
	var file string
	var funcName string
	var line int
	if entry.Caller != nil {
		file = filepath.Base(entry.Caller.File)
		funcName = filepath.Base(entry.Caller.Function)
		line = entry.Caller.Line
	}

	var info string
	if entry.Data["info"] != nil {
		infoString, err := JsonEncoder(entry.Data["info"], false)
		//infoByte, err := json.Marshal(entry.Data["info"])
		if err != nil {
			info = fmt.Sprintf("  >>> info=%v",entry.Data["info"])
		} else {
			info = "  >>> info=" + infoString
		}
	}else{
		info = ""
	}

	var commonInfo string
	if entry.Data["common_info"] != nil {
		//infoByte, err := json.Marshal(entry.Data["common_info"])
		infoString, err := JsonEncoder(entry.Data["common_info"], false)
		if err != nil {
			commonInfo = fmt.Sprintf("  >>> common_info=%v",entry.Data["common_info"])
		} else {
			commonInfo = "  >>> common_info=" + infoString
		}
	}else{
		commonInfo = ""
	}

	var tag, route, exctime, status string

	if  cast.ToString(entry.Data["tag"]) != "" {
		tag = fmt.Sprintf(" [tag:%s]", entry.Data["tag"])
	}

	if  cast.ToString(entry.Data["route"]) != "" {
		route = fmt.Sprintf(" [route:%s]", entry.Data["route"])
	}

	if cast.ToString(entry.Data["exc_time"]) != "" {
		exctime = fmt.Sprintf(" [exc_time:%s",entry.Data["exc_time"])
	}

	if cast.ToInt(entry.Data["st_code"]) > 0 || cast.ToString(entry.Data["code"]) != "" {
		status = fmt.Sprintf(" [status:%v/%s]", entry.Data["st_code"], entry.Data["code"])
	}

	/*
		完整日志格式
		time [loglevel] [type] [http_status/code] [exc_time] [file(method):line] [route] [tag:tag] msg\n\t info=xxx"
		其中http_status/code, exc_time, route, tag 将根据是否有值进行显示或隐藏
		如果指定的上下文，则会加上route path，如果指定了tag，还会加上tag
	*/
	msg := fmt.Sprintf("%s [%s] [%s]%s%s [%s(%s):%d]%s%s%s%s%s\n",
		timestamp,
		entry.Level.String(),
		entry.Data["type"],
		status,
		exctime,
		file,
		funcName,
		line,
		route,
		tag,
		entry.Message,
		commonInfo,
		info,
	)

	b.WriteString(msg)
	return b.Bytes(), nil
}


func initLoggers(logs []LogOption) {
	loggers = make(map[string]*Logger)
	for _, logOption := range logs {
		log := Logger{Logger:logrus.New()}
		log.SetReportCaller(true)
		if err := CreateFile(logOption.FileName); err != nil {
			fmt.Println( "日志初始化错误：",logOption.Name, logOption.FileName, err)
		}
		//设置日志级别
		log.SetLevel(logrus.DebugLevel)

		//设置日志格式
		if strings.ToLower(logOption.Format) == "json" {
			log.SetFormatter(&logrus.JSONFormatter{
				TimestampFormat:   "2006-01-02 15:04:05",
			})
		}else{
			log.SetFormatter(new(LogFormatter))
		}


		//设置日志分割
		logWriter, err  := rotatelogs.New(
			// 分割后的文件名称
			logOption.FileName + ".%Y%m%d.log",
			//生成软链， 指向最新日志文件
			rotatelogs.WithLinkName(logOption.FileName),
			//设置最大保存时间
			rotatelogs.WithMaxAge(365 * 24 * time.Hour),
			//设置日志切割时间间隔(1天)
			rotatelogs.WithRotationTime(24*time.Hour),
		)
		//设置日志输出
		log.SetOutput(logWriter)
		// log.Out = src
		if err != nil {
			fmt.Println("日志文件分割错误：",logOption.Name, logOption.FileName, err)
		}
		loggers[logOption.Name] = &log

		fmt.Println("日志初始化成功", logOption.FileName)

		if logOption.Name == option.DefaultLogName {
			errStaceLogOut = log.Out
			SqlLogOut = log.Out
			cronLogOut = log.Out
			// 初始化错误跟踪日志输出
			//fileName := fmt.Sprintf("%s.err_stack.log", logOption.FileName)
			//if err = CreateFile(fileName); err != nil{
			//	fmt.Println( "错误跟踪日志初始化错误：",fileName, err)
			//}
			//if errStaceLogOut, err = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil {
			//	fmt.Println( "错误跟踪日志初始化错误：",fileName, err)
			//}
			//fmt.Println( "错误跟踪日志初始化成功：",fileName)
			//// 初始化SQL日志输出
			//fileName = fmt.Sprintf("%s.sql.log", logOption.FileName)
			//if err = CreateFile(fileName); err != nil {
			//	fmt.Println("SQL日志初始化错误：", fileName, err)
			//}
			//if SqlLogOut, err = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil {
			//	fmt.Println("SQL日志初始化错误：", fileName, err)
			//}
			//fmt.Println( "SQL日志初始化错误：",fileName)
			//// 初始化任务跟踪日志输出
			//fileName = fmt.Sprintf("%s.cron.log", logOption.FileName)
			//if err = CreateFile(fileName); err != nil{
			//	fmt.Println( "任务计划日志初始化错误：",fileName, err)
			//}
			//if cronLogOut, err = os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend); err != nil {
			//	fmt.Println( "任务计划日志初始化错误：",fileName, err)
			//}
			//fmt.Println( "任务计划日志初始化成功：",fileName)
		}
	}
}


// 获取一个日志记录器
func GetLogger(name string) *Logger {
	logger := loggers[name]
	logger.tag = ""
	logger.commonInfo = nil
	return logger
}

// 获取默认日志记录器
func GetDefaultLogger() *Logger {
	return GetLogger(option.DefaultLogName)
}

// 获取一个上下文日志记录器
func GetContextLogger(name string, c *gin.Context) *Logger {
	logger := GetLogger(name)
	logger.c = c
	return logger
}
// 获取一个默认上下文日志记录器
func GetDefaultContextLogger(c *gin.Context) *Logger {
	return GetContextLogger(option.DefaultLogName, c)
}

func WriteCronLog(name string,msg string) {
	cronLogInfo := fmt.Sprintf(`%s  %s 
%s
`, time.Now().Format("2006-01-02 15:04:05"), name, msg)
	if _, err := io.WriteString(cronLogOut, cronLogInfo); err != nil {
		fmt.Println("任务计划日志写入发生异常：", err)
		fmt.Println("待写入的信息是：", name, msg)
	}
}

// 将错误追踪层级信息写入日志
func WriteErrStackLog(c *gin.Context, errMsg string) {
	var contextStackErr string
	if c != nil {
		contextStackErr = GetStackErr(c) // 获取上下文保存的跟踪错误信息
		SetStackErr(c,"") //清空
	}

	// 将错误追踪层级信息写入日志
	stackErr := fmt.Sprintf(`%s	%s
%s
`, time.Now().Format("2006-01-02 15:04:05"), errMsg, contextStackErr)


	if _, err := io.WriteString(errStaceLogOut, stackErr); err != nil {
		fmt.Println("错误追踪写入日志发生异常：", err)
		fmt.Println("待写入的错误信息：", stackErr)
	}
	if option.Debug {
		fmt.Println(stackErr)
	}
}